-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEP-366: Meta transactions #366
Conversation
Your Render PR Server URL is https://nomicon-pr-366.onrender.com. Follow its progress at https://dashboard.render.com/static/srv-caqccvcgqg418bac0utg. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this proposal! Wanted to suggest some small spelling/grammar changes, but overall looks like a huge UX improvement for new users
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meta comment (no pun intended): we should separate proposal from modification to the spec, i.e, the proposal strictly follows https://github.com/near/NEPs/blob/master/nep-0000-template.md and the spec is updated in a separate PR if the proposal is accepted and implemented. This way it makes the proposal more self-contained and easier to review.
The proposal leaves some things out and is a bit ambiguous, especially given that DelegateAction specification is a broken link. The following two things would significantly improve my understanding:
The following things contribute to my confusion:
So
So the signer is not the author of the transaction but the relayer? I suggest we find a different naming here, since |
neps/nep-0366.md
Outdated
The main flow of the meta transaction will be as follows: | ||
- User will sign `DelegateActionMessage` specifying set of actions that they need to be executed. It also includes specific relayer to ensure secure execution. | ||
- User sends signed `DelegateAction` data to the relayer | ||
- Relayer forms a `Transaction` with `receiver_id` equal to the user's account and `actions: [DelegateAction { ... }]`, and signs it with it's key. Note, that such transaction can contain other actions toward user's account (for example calling a function). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify, do you mean here that the protocol specifies that for meta transactions, the actions
field is a singleton that consists of one DelegateAction
? In other words, if a transaction contains more than one action and one of them is DelegateAction
, then this transaction is invalid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if that was the goal, but I don't think having this as a singleton is mandatory.
neps/nep-0366.md
Outdated
/// Nonce must be account[signer_pk].nonce + 1 | ||
DelegateActionInvalidNonce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we enforce +1
here? That is not how access key works in other cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bowenwang1996 Can you share a reference about how nonces work today? I can't find it in nomicon.
As this was discussed today during the Protocol meeting, I wonder if this is still relevant or has it been moved/migrated to a different place? (in accordance with the new NEP process?) |
This is a draft implementation of NEP-366 (near/NEPs#366) Not done yet: * Need to implement DelegateAction for implicit accounts (nonce problem) * Need new error codes for DelegateAction * Implement Fees for DelegateAction * Add tests
This is a draft implementation of NEP-366 (near/NEPs#366) Not done yet: * Need to implement DelegateAction for implicit accounts (nonce problem) * Need new error codes for DelegateAction * Implement Fees for DelegateAction * Add tests
We've created a draft reference implementation of meta transactions here: near/nearcore#7497 (WIP) |
neps/nep-0366.md
Outdated
- This transaction is processed normally, by creating a receipt with copy of the all the actions into the `Receipt`. | ||
- When processing `DelegateAction` a number of checks are done (see below), mainly `signature` matching user account's key. | ||
- When such `Receipt` with valid `DelegateAction` in actions arrives to the user's account it gets executed. The executed means creation of a new Receipt with `receiver_id: AccountId`, `actions: Action` matching `receiver_id` and `actions` inside `DelegateAction`. | ||
- The new `Receipt` looks like normal receipt that would have been originating from user's account, with `predeccessor_id` equal to user's account. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear what signer_id
should be equal to. According to the discussion in near/nearcore#7497 it should be equal to Relayer's account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note that using signer_id
should be considered unsafe most of the time. See this comment for the equivalent in Ethereum: ethereum/solidity#683 (comment).
Having said that I think signer_id should be the account that is paying for the attached balance (not gas), and if I understand correctly it is not the relayer, but the user signing the transaction.
neps/nep-0366.md
Outdated
|
||
The main flow of the meta transaction will be as follows: | ||
- User will sign `DelegateActionMessage` specifying set of actions that they need to be executed. It also includes specific relayer to ensure secure execution. | ||
- User sends signed `DelegateAction` data to the relayer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a user send DelegateAction
data to the relayer out off-chain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the advantage of having two structures: DelegateActionMessage
and DelegateAction
? A user can send DelegateActionMessage
to the relayer and the realyer can add DelegateActionMessage
to the transaction as an action. I think, this is simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for explaining more clearly the role of the two messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also - does user send a DelegateAction or DelegateActionMessage (based on context, I think it is the latter - as this is the one that was created in the previous point)
This NEP doesn't cover an implicit account. It would be good if an implicit account could use a delegate action. The discussion was started in near/nearcore#7497. |
/// Receiver of the delegated actions. | ||
receiver_id: AccountId, | ||
/// List of actions to be executed. | ||
actions: Vec<Action>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vec<Action>
here leads to a type recursion:
pub struct Transaction {
....
pub actions: Vec<Action>, // <--- Recursion
}
pub enum Action {
...
Delegate(DelegateAction),
}
pub struct DelegateAction {
...
pub actions: Vec<Action>, /// <--- Recursion
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is acceptable in this context because an Action can't refer to itself or an ancestor, so recursion is finite.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant, rust compiler would not compile such code because of the recursion.
Example code in playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a6c54d6ed7588eb03d2ad19da1c0a9c6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, a common way to prevent this recursion is to use a Box
. As an example, a singly-linked list in Rust can be represented as:
struct Node<T> {
element: T,
next: Option<Box<Node<T>>>,
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=14f24298eee755a2a08c7b5b66d8b4fa suggests a fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. When I wrote my comment, I didn't know that Borsh supported Box
. But there is another issue with this structure: Borsh doesn't support a recursive types near/borsh-rs#96
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in the video call, for the purposes of the NEP, the recursive data structure is fine. For nearcore
, we will have to come up with a workaround. Best to discuss on Zulip.
@bowenwang1996 @mm-near @mfornet – We moved this NEP to the Review stage. Could you please assign at least 2 SME Reviewers to review the NEP within one week? Per our NEP process, the reviewers will review the technical details of the proposal and assess its merits. They may ask clarification questions, request changes, or reject the proposal. |
@ori-near @mm-near @mfornet @bowenwang1996 Hi! Please, consider, that the practical PoC of the NEP was introduced here: near/nearcore#7497. The comments of @e-uleyskiy in here are based on the issues uncovered during the practical implementation. There are few improvements yet to get implemented which we are working on. Please let us know in case of kicking off any parallel development activities and in case of any updates. Thank you! |
I'd like to add more context to the discussion. Fungible tokens (FT) are smart contracts, it's essentially an opaque application layer (opaque from the blockchain layer perspective), and it means that there is no way the blockchain can guarantee that the Relayer gets its reward with FT. What blockchain can guarantee:
Everything else should be done on the "application" level, and some security verification pieces lay on the Relayer. Here are 2 practical use cases which are feasible considering this NEP implementation: UC 1: Transferring FTUC 2: Calling a DApp (smart contract)Bottom lineRelayer should have trust in the target smart contract using out-of-bound (off-chain) mechanism of establishing such trust to some extent.
At the same time, with Meta Transaction, Relayer doesn't need to have a trust to Alice (the user):
|
@fadeevab thanks for the clear presentation of two possible use cases! That's exactly what we need to bring everyone on the same page about what is possible and what is not possible with the current proposal.
Can you elaborate more on that? What would be the protocol's role here? What would happen on the application layer? |
Ideally, all such considerations should be included in the "Drawback" section, so working group members don't suddenly rediscover them and start going in the loops. I think the main concern that blocked united voting for approval was the fact that it needs some time to think through the pros and cons of approving the NEP as-is. We are not living in an ideal world, so having NEPs with drawbacks is totally normal as long as it does not introduce a security flaw. Personally, I believe that if Decentral Bank and Aurora will benefit from having this NEP as-is, it is already a huge win and the only question is "is there a quick fix that would unlock even more use cases?" It sounds like you guys have already thought about it and there is no quick fix, and if that is the case, it sounded to me that the protocol working group is ready to approve this NEP as-is (@mfornet @bowenwang1996 @mm-near correct me if I am wrong 🙏). @e-uleyskiy @fadeevab Please, include all the known drawbacks in the NEP itself.
I believe that the main concern was around the use case when the relayer expects on-chain payment per transaction and the trust issue is on both sides: Alice doesn't want to pay ahead of time, Relayer does not want to be abused. Thinking about it now (again, there was no time to properly weigh this concern during the call), I don't think it is the main use case that Meta Transactions feature is trying to address, as it is clearly stated in the motivation section of the NEP:
|
I don't know nearcore code deeply. Below is my understanding. Near protocol guarantee that the only actions are executed in the current block are rolled back. So if the called contract function doesn't contain cross-contract calls and it fails, then the protocol guarantee this In other words, if |
Yes, you got that exactly right @e-uleyskiy . Near Protocol can only rollback actions within the same receipt, we cannot rollback on a transaction level. (I find the term transaction particularly confusing in this context but what I mean is the transitive set of receipts caused by a In the following part it wasn't 100% clear to me you were talking about single receipt rollback only.
Some people will read "Meta Transaction" and think of "A pays for a transaction on B sent by C" regardless of what you just wrote aboe that quote. The important part to get across here is that the rollback you refer to only works with a single delegate action and would be broken if we make it a list. Otheriwse it will be two different receipt, hence not atomic. And to be clear, @fadeevab you have already written this correctly and well articulated. I just double down on this to make sure we don't have another misunderstanding of what a "Meta Transaction" is exactly. The final NEP to vote on after the delay should also be as unambiguous about this as possible. |
The main reason we didn't approve this NEP during the call was due to the difficulty of the following use case: The relayer will execute a transaction on behalf of the user, covering the NEAR gas, but it needs to make sure it will be paid in the same transaction (potentially in the form of an FT). Guaranteeing that the payment will go through during the same transaction at the protocol level is hard due to the async nature of NEAR + FT being part of an opaque application layer. After thinking more about this, I have the impression we can have this NEP as is and try to solve the previous problem at the application level. There will be several kinds of relayers with different tradeoffs. The first few relayers I foresee are centralized-like:
Building decentralized relayers should be possible using two transactions (this was proposed in the call). One is where the user sends tokens to the relayer, and the other is with the actual transaction. To make this work in a trustless setup, the transactions where the relayer claims the tokens (execute the transfer) should have as input proof that the main transaction was executed (this can be done naively using Merkle proofs, but potentially it can be done in a simpler way if the protocol is modified appropriately). Btw, I think there are even more solutions to this problem, for example, using HTLC. |
If we allow contracts to submit Something like the following: @frol |
@jakmeier @mfornet @frol and others
|
@fadeevab - thanks a lot for your explanations above. With this knowledge, I'm ready to approve this NEP. |
I've updated drawbacks: #437 |
Co-authored-by: Egor Uleyskiy <[email protected]>
@fadeevab @e-uleyskiy thanks for the detailed explanations and diagrams! I am ready to approve the NEP as is. |
Just to complete the discussion from the call: Am I correctly assuming that we don't want to turn DelegateAction to support (receiver_id, actions) due to increase in complexity that will not address the problem at hand? And indeed 2 DelegateAction can be submitted in a single transaction by relayer that would work in similar way as having a list of (receiver_id, actions) in DelegateAction as there is no atomiticty there. I also would like to address the business concern of "relayer doesn't trust user and user doesn't trust relayer" -- here I think need in user trust is not business critical as they are selecting relayer, paying small amounts and can stop using given relayer if it doesn't really perform the required job. So getting user to pay in a separate action that is non atomic with the action they want to execute would be a normal business practice. Similarly how @mfornet mentioned that other paths of payment can be establish like pre-payment or subscription.
@e-uleyskiy This was part of original intention to allow contracts to initiate DelegateActions as this opens up for other use cases like cron jobs: user pre-signs transaction to be executed by specific contract and records it there; when some condition is hit and contract has been activated this DelegateAction gets executed. Indeed this can be used in case of pay -> call back to relayer to than execute the DelegateAction with main action.
This NEP doesn't include a precompile to initiate DelegateAction out of smart contract? Should be done in a separate NEP? PS. I would add that if we had FT standard that stored amounts on the user account (e.g. account extensions/global contract storage/user storage line of thinking) it would allow to do payment and further execution in atomic action. But that is the story for another day. |
Yes |
As the NEP moderator, I would like to give a huge thank you to the original author @ilblackdragon for submitting this NEP and to the champions @e-uleyskiy @fadeevab who worked so hard to move it forward and address the concerns. Thank you also to the Subject Matter Experts (@jakmeier @akhi3030) and Protocol Working Group members (@bowenwang1996, @mfornet, @mm-near, @k3y0k3y) for all your reviews. Based on the voting above, the Protocol Working Group members reached following consensus: Status: Approved
Next Steps:
|
Yes, should be done in a separate NEP. I think it would be a good feature because then:
|
- If the list of Transaction actions contains several `DelegateAction` | ||
```rust | ||
/// There should be the only one DelegateAction | ||
DelegateActionMustBeOnlyOne |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious why this is the case? I don't see the reasoning in this doc; sorry if I skimmed over it. This seems like a benefit to have to be able to schedule multiple meta transactions within a single transaction, to minimize costs for the relayer.
If you actually want this to be the only thing executed within the transaction, should regular actions not be allowed with a delegate action? Currently, the draft implementation seems to allow other actions with a delegate action, and I just want to make sure this is intended
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@austinabell
I added this case for two reasons:
- At this moment all actions in the transaction are executed in one by one. But in case of
DelegateAction
, they will be executed in parallel (inner actions will be executed in parallel). I think such behavior might be confusing for developer/users. - For code simplification. If we allow several
DelegateAction
in a transaction, we should decide how limits should be handled. For example there is the limit for total number of actions in the transactions. Should it be shared betweenDelegateAction
and the transaction or we need to define a new limit for number of actions inDelegateAction
?
I'm reading about the Meta Transactions. One thing is not clear to me, and is not documented well in the NEP: |
@robert-zaremba Yeah, the NEP description isn't 100% clear about all details. But it is indeed the relayer who pays the balance fees on top of the gas fees. The limitation section speaks specifically about the corner case where the user account doesn't exist. That's not an expected workflow, it's an error. It would only happen if you use the wrong account id for the meta transaction, or if you somehow delete your account before the meta transaction has a chance to execute. If that case happens, yes, the attached tokens are burned because implementing it in another way would require larger changes to the protocol (e.g. adding a "relayer_id" field to all receipts).
If by "new" account mean one that has not been created, yet, then it's specifically forbidden with meta transactions. You or the relayer must create the account first (to initialize the Nonce counter) before you can create a meta transaction. If it's an account which was created but it just has no balance, nothing will be burned. And btw, as of right now, you will find the most extensive meta-tx documentation in the nearcore developer guide, you might want to take a look there while other documentation is still written: https://near.github.io/nearcore/architecture/how/meta-tx.html |
Nice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😎😎
Summary
In-protocol meta transactions allowing for third-party account to initiate and pay transaction fees on behalf of the account.
Motivation
NEAR has been designed with simplify of onboarding in mind. One of the large hurdles right now is that after creating an implicit or even named account user doesn't have NEAR to pay gas fees to interact with apps.
For example, apps that pay user for doing work (like NEARCrowd or Sweatcoin) or free-to-play games.
Aurora Plus has shown viability of the relayers that can offer some number of free transactions and a subscription model. Shifting the complexity of dealing with fees to the infrastructure from the user space.
Rationale and alternatives
Proposed here design provides the easiest for users and developers way to onboard and pay for user transactions.
There is no requirement on the user account, including user account may not even exist on chain and implicit account can be used.
An alterantive is a proxy contracts deployed on the user account.
This design has severe limitations as it requires user to deploy such contract and additional costs for storage.
Specification
Next changes to protocol are required:
DelegateAction(receiver_id, Action, signer_pk, signature)
DelegateAction
to thereceiver_id
.signature
over the given account andsigner_pk
. Unwraps intoreceiver_id: AccountId
,action: Action
inside into new Receipt with givenreceiver_id
andaction
. This means thatpredeccessor_id
of such action has been overriden.See the DelegateAction specification for details.
Security Implications
Delegate actions do not override
signer_pk
, leaving that to the original signer that initiated transaction (eg relayer in meta transaction case). Although it is possible to overridesigner_pk
in the context with one fromDelegateAction
, there is no clear value to do that.See Validation section in DelegateAction specification for security considerations around what user signs and validation of actions with different permissions.
Drawbacks
Increases complexity of the NEAR's transactional model.
Meta transactions take an extra block to execute, as they first need to be included by the originating account, then routed to the delegate account and only after that to the real destination.
Delegate actions are not programmable as they require having signatures. Original proposal contained a new
AccessKey
kind that would support programmable delegated actions. On the other hand, that would be limiting without progamability of smart contracts, hence that idea evolved into Account Extensions.Future possibilities
Supporting ZK proofs instead of just signatures can allow for anonymous transactions, which pay fees to relayers in anonymous way.
Copyright
Copyright and related rights waived via CC0.